﻿namespace Codeplex.KinkumaFramework.Behaviors
{
    using System;
    using System.Collections.Specialized;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Interactivity;
    using Codeplex.Reactive.Extensions;

    /// <summary>
    /// This behavior add ReactiveProperty&lt;T&gt; support at ValidationSummary.
    /// </summary>
    public class ValidationSummaryReactivePropertySupportBehavior : Behavior<ValidationSummary>
    {
        internal static string GetReactivePropertyDisplayName(DependencyObject obj)
        {
            return (string)obj.GetValue(ReactivePropertyDisplayNameProperty);
        }

        internal static void SetReactivePropertyDisplayName(DependencyObject obj, string value)
        {
            obj.SetValue(ReactivePropertyDisplayNameProperty, value);
        }

        internal static readonly DependencyProperty ReactivePropertyDisplayNameProperty =
            DependencyProperty.RegisterAttached("ReactivePropertyDisplayName", typeof(string), typeof(ValidationSummaryReactivePropertySupportBehavior), new PropertyMetadata(null));

        private IDisposable disposable;

        protected override void OnAttached()
        {
            if (this.disposable != null)
            {
                this.disposable.Dispose();
            }

            this.disposable = this.AssociatedObject
                .Errors
                .ObserveAddChanged()
                .Subscribe(item =>
                {
                    if (item.MessageHeader == "Value")
                    {
                        item.MessageHeader = ParseReactivePropertyDescription(item) ?? item.MessageHeader;
                    }
                });

            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            if (this.disposable != null)
            {
                this.disposable.Dispose();
                this.disposable = null;
            }

            base.OnDetaching();
        }

        private string ParseReactivePropertyDescription(ValidationSummaryItem vr)
        {
            if (vr.Sources.Count == 0)
            {
                return null;
            }

            var control = vr.Sources[0].Control as FrameworkElement;

            var displayName = GetReactivePropertyDisplayName(control);
            if (!string.IsNullOrEmpty(displayName))
            {
                return displayName;
            }

            var targetBinding = ParseControl(control);
            if (targetBinding == null)
            {
                return null;
            }

            var property = ParseBindingExpression(targetBinding);
            if (property == null)
            {
                return null;
            }

            var message = property.GetCustomAttributes(typeof(DisplayAttribute), false)
                .Cast<DisplayAttribute>()
                .Select(d => d.Name)
                .FirstOrDefault();
            if (!string.IsNullOrEmpty(message))
            {
                SetReactivePropertyDisplayName(control, message);
            }

            return message;
        }

        /// <summary>
        /// lookup propertyinfo in binding expression and object.
        /// </summary>
        private static PropertyInfo ParseBindingExpression(Tuple<object, BindingExpression> targetBinding)
        {
            var properties = targetBinding.Item2
                .ParentBinding
                .Path
                .Path
                .Split('.');

            if (properties.Length == 1)
            {
                return targetBinding.Item1.GetType().GetProperty(properties.First());
            }

            return properties.Skip(1).Take(properties.Length - 2)
                .Aggregate(
                    targetBinding.Item1.GetType().GetProperty(properties.First()),
                    (propertyInfo, propertyName) =>
                    {
                        if (propertyInfo == null)
                        {
                            return null;
                        }

                        var ret = propertyInfo.PropertyType.GetProperty(propertyName);
                        if (ret == null || !ret.CanRead)
                        {
                            return null;
                        }

                        return ret;
                    });
        }

        /// <summary>
        /// lookup datacontext and bindingexpression in control's dependencyproperties.
        /// </summary>
        private static Tuple<object, BindingExpression> ParseControl(FrameworkElement control)
        {
            var fields = control.GetType().GetFields(
                BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            return fields.Where(f => typeof(DependencyProperty).IsAssignableFrom(f.FieldType))
                .Select(f => f.GetValue(null) as DependencyProperty)
                .Select(dp => control.GetBindingExpression(dp))
                .Where(be => be != null && be.ParentBinding != null)
                .Where(be => be.ParentBinding.Mode == BindingMode.TwoWay) // only TwoWay binding
                .Where(be => be.ParentBinding.Path != null && be.ParentBinding.Path.Path.EndsWith(".Value")) // only reactiveproperty
                .Select(be => Tuple.Create(be.DataItem ?? control.DataContext, be))
                .FirstOrDefault();
        }

    }
}
